/*
 * CEngineProcess - engine wrapper. Used to load/control the original UCI engine. 
 * Kannan Ramanathan
 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;

namespace PCAD.CAT
{
    //declaring the read event handler delegate
    public delegate void DataReadHandler(int uniqueID, string data);    

    public class CEngineProcess
    {
        /// <summary>
        /// Occurs when data_read operation takes place. 
        /// Users should register their callbacks here before
        /// invoking asynchronous_read
        /// </summary>
        public static event DataReadHandler DataRead;

        private string _engineName;
        private Process engineProcess = null;
        private bool engineLoaded = false;

        /// <summary>
        /// Gets the unique engine id. 
        /// Users should store this for every instance 
        /// of CEngineProcess created
        /// </summary>
        /// <value>The engine id.</value>
        public int engineId 
        {
            get 
            {
                if (isEngineLoaded())
                {
                    return engineProcess.Id;
                }
                return 0;
            }
        }

        /// <summary>
        /// Gets or sets the name of the engine.
        /// </summary>
        /// <value>The name of the engine.</value>
        public string engineName
        {
            get { return _engineName; }
            set { _engineName = value;}
        }

        /// <summary>
        /// Gets the engine output.
        /// </summary>
        /// <value>The engine output stream.</value>
        public System.IO.StreamReader engineOutput
        {
            get { return engineProcess.StandardOutput; }
        }

        /// <summary>
        /// Gets the engine input.
        /// </summary>
        /// <value>The engine input stream.</value>
        public System.IO.StreamWriter engineInput
        {
            get { return engineProcess.StandardInput; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CEngineProcess"/> class.
        /// </summary>
        /// <param name="engineName">Name of the engine (string).</param>
        public CEngineProcess(string engineName)
        {
            this.engineName = engineName;
            engineProcess = new Process();
        }

        /// <summary>
        /// Determines whether [is engine loaded].
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if engine is loaded, otherwise, <c>false</c>.
        /// </returns>
        public bool isEngineLoaded()
        {
            return engineLoaded;
        }

        /// <summary>
        /// Sends the engine command.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <returns>TRUE if command could be sent to engine, FALSE otherwise</returns>
        public bool sendEngineCommand(string command)
        {
            bool retCode = false;

            if (String.IsNullOrEmpty(command))
            {
                return true; /// No error occurred. So return true. 
            }
            try
            {
                if (isEngineLoaded())
                {
                    engineInput.Write(command);
                    retCode = true;
                }
            }
            catch (Exception e)
            {
                handleException(e);
                return false;
            }

            return retCode;
        }

        /// <summary>
        /// Sends the engine command CRLF.
        /// </summary>
        /// <param name="command">The command.</param>
        public void sendEngineCommandCRLF(string command)
        {
            command += "\n";
            sendEngineCommand(command);
        }

        /// <summary>
        /// Tests all functions.
        /// 
        /// ***** INTERNAL TEST FUNCTION. NOT FOR PUBLIC USE ***********
        /// </summary>        
        /// <returns>TRUE if successfull, FALSE otherwise</returns>
        private bool testAllFunctions()
        {
            bool retCode = false;

            loadEngine();
            
            engineInput.AutoFlush = true;

            DataRead += new DataReadHandler(CEngineProcess_DataRead);

            sendEngineCommandCRLF("uci");
            readEngineOutputLineAsynchronous();
            
            sendEngineCommandCRLF("quit");
        
            return retCode;
        }

        /// <summary>
        /// Waits for engine to exit.
        /// </summary>
        public void waitForExit()
        {
            if (isEngineLoaded())
            {
                engineProcess.WaitForExit();
                engineProcess.Close();
            }
        }

        /// <summary>
        /// Callback for engineprocess data_read.
        /// **** INTERNAL. NOT for public use *****
        /// </summary>
        /// <param name="id">The id.</param>
        /// <param name="data">The data.</param>
        void CEngineProcess_DataRead(int id, string data)
        {
            Console.WriteLine("PGM::{0}::{1}",  id, data);
        }

        /// <summary>
        /// Handles the OutputDataReceived event of the engineProcess control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Diagnostics.DataReceivedEventArgs"/> instance containing the event data.</param>
        void engineProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //Console.WriteLine("DEBUG::"+ (Process) );
                if (DataRead != null) 
                    DataRead(((Process) sender).Id, e.Data);
            }
        }

        /// <summary>
        /// Loads the engine.
        /// </summary>
        /// <returns>TRUE if success, FALSE otherwise</returns>
        public bool loadEngine()
        {
            bool retCode = false;

            try
            {
                if (!string.IsNullOrEmpty(this.engineName))
                {
                    /// Populate the StartInfo struct. This is used as an input for Process class
                    engineProcess.StartInfo.FileName = this.engineName;
                    engineProcess.StartInfo.CreateNoWindow = true;
                    engineProcess.StartInfo.UseShellExecute = false;
                    engineProcess.StartInfo.RedirectStandardInput = true;
                    engineProcess.StartInfo.RedirectStandardOutput = true;
                    
                    /// Setup the asynchronous READ_DATA handler
                    engineProcess.OutputDataReceived += new DataReceivedEventHandler(engineProcess_OutputDataReceived);

                    /// Execute the engine now
                    if (engineProcess.Start() == true)
                    {
                        engineInput.AutoFlush = true;
                        
                        /// Set a flag for other methods to check [Internal]
                        engineLoaded = true;

                    }                                 
                    else
                    {
                        throw new Exception("Process creation error: Failed to load the engine"
                            + this.engineName);
                    }
                }
                return retCode;
            }
            catch (Exception e)
            {
                handleException(e);
                return false;   /// Signal a failure since we have caught an exception
            }
        }

        /// <summary>
        /// Reads the engine output line asynchronous.
        /// </summary>
        public void readEngineOutputLineAsynchronous()
        {
            if (isEngineLoaded())
            {
                try
                {
                    engineProcess.BeginOutputReadLine();
                }
                catch (Exception e)
                {
                    handleException(e);
                }
            }
        }

        /// <summary>
        /// Handles the exception.
        /// </summary>
        /// <param name="e">The Exception context.</param>
        private void handleException(Exception e)
        {
            string s = e.Message + "\n" + e.ToString();
            Console.WriteLine(s);
        }        
    }
}          